package com.huxi.lang.io;

import java.io.Closeable;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.huxi.lang.text.TextUtils;

/**
 * ZIP文件流内容写入
 * 
 * @author jian.wu
 *
 */
public class ZipWriter implements Closeable {

	private ZipOutputStream zip;
	private boolean closed;

	public ZipWriter(OutputStream stream) {
		this.zip = new ZipOutputStream(Objects.requireNonNull(stream));
		this.closed = false;
	}

	public boolean isClosed() {
		return closed;
	}

	public void flush() throws IOException {
		zip.flush();
	}

	@Override
	public void close() throws IOException {
		if (!closed) {
			closed = true;
			IOException en = null;
			try {
				zip.finish();
			} catch (IOException ee) {
				en = ee;
			}
			zip.close();
			if (en != null) {
				throw en;
			}
		}
	}

	/**
	 * 写入目录结构
	 * 
	 * @param path 目录路劲，非空。
	 * @return 目录路劲
	 * @throws IOException ZIP文件流内容写入目录条目失败
	 */
	public String writeDirectory(String path) throws IOException {
		String mpath = checkEntryPath(path, true);
		ZipEntry entry = new ZipEntry(mpath);
		zip.putNextEntry(entry);
		zip.closeEntry();
		return mpath;
	}

	/**
	 * 写入文件内容
	 * 
	 * @param path     文件路劲，非空。
	 * @param consumer 内容生成器，非空值。
	 * @return 文件路劲
	 * @throws IOException ZIP文件流内容写入目录条目失败
	 */
	public String writeFile(String path, Consumer<OutputStream> consumer) throws IOException {
		Objects.requireNonNull(consumer, "内容生成器参数不能为空值");
		String mpath = checkEntryPath(path, false);
		try {
			ZipEntry entry = new ZipEntry(mpath);
			zip.putNextEntry(entry);
			consumer.accept(new ZipEntryOutputStream(zip, entry));
		} catch (IOException en) {
			throw en;
		} catch (IORuntimeException en) {
			throw en.toIOException();
		} catch (Exception en) {
			throw en;
		} finally {
			if (path != null) {
				try {
					zip.closeEntry();
				} catch (IOException en) {
					// 条目内容已写入结束，忽略此异常
				}
			}
		}
		return mpath;
	}

	/**
	 * ZIP文件中追加文件条目
	 * 
	 * @param path 条目路径，非空值。
	 * @return 文件内容输出流，非空值。
	 * @throws IOException 追加失败
	 */
	public ZipEntryOutputStream appendFile(String path) throws IOException {
		String mpath = checkEntryPath(path, false);
		ZipEntry entry = new ZipEntry(mpath);
		zip.putNextEntry(entry);
		return new ZipEntryOutputStream(zip, entry);
	}

	protected static String checkEntryPath(String path, boolean directory) {
		TextUtils.requireNonEmpty(path, "目录路劲参数不能为空");
		String mpath = path.replaceAll("\\\\", "/").replaceFirst("/+$", "");
		if (mpath.length() == 0) {
			throw new IllegalArgumentException("ZIP文件条目路径不规范: " + path);
		}
		if (directory) {
			return mpath + "/";
		} else {
			return mpath;
		}
	}

	public static class ZipEntryOutputStream extends FilterOutputStream {

		private final ZipEntry entry;

		private ZipEntryOutputStream(ZipOutputStream stream, ZipEntry entry) {
			super(stream);
			this.entry = entry;
		}

		public ZipEntry getEntry() {
			return entry;
		}

		public String getPath() {
			return entry.getName();
		}

		@Override
		public void close() throws IOException {
			((ZipOutputStream) out).closeEntry();
		}
	}

}
